這次我們由從 index 的連結開始
在 notes/index.html.eex
中加上修改的連結,將 <ul>
列表改為:
<ul class="flex flex-col gap-4">
<li
:for={note <- @notes}
class="flex justify-between bg-amber-50 p-4 rounded-lg font-bold border-2 border-amber-300"
>
<%= note.content %>
<a href={~p"/notes/#{note}/edit"} class="text-amber-800">編輯</a>
</li>
</ul>
在 Router 裡面定義的編輯頁面方式為:
get "/notes/:id/edit", NoteController, :edit
這裡導向編輯的連結使用了 note.id,我們使用 ~p
sigil 加上字串插值來產生連結。
(#{note.id}
可省略為 #{note}
)
當然,點進去之後還是抱怨了找不到 edit
,我們接著實作。
修改的 edit/update 頁面與新增的 new/create 頁面非常相似,唯一的不同是,新增時我們使用空的 note struct (%Note{}
) 來建立 changeset,而修改時我們必須要使用 Notes.get_note
來取得目前的 note struct,並使用它來建立 changeset。
替目前的 NotesController 加上修改的 edit/update 兩個函式
def edit(conn, %{"id" => id}) do
note = Notes.get_note(id)
changeset = Note.changeset(note, %{})
render(conn, :edit, note: note, changeset: changeset)
end
def update(conn, %{"id" => id, "note" => note_params}) do
note = Notes.get_note(id)
case Notes.update_note(note, note_params) do
{:ok, _note} ->
conn
|> put_flash(:info, "感激筆記修改成功")
|> redirect(to: ~p"/notes")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :edit, note: note, changeset: changeset)
end
end
在 lib/gratitude_web/views/note_view.ex
中加上 edit.html.eex
的 view
<header class="my-4 border-b-4 border-amber-600">
<h1 class="text-xl font-bold text-amber-800">編輯感激筆記</h1>
</header>
<.form :let={form} for={@changeset} action={~p"/notes/#{@note}"}>
<.input field={form[:content]} />
<div class="flex justify-between items-center mt-4">
<a href={~p"/notes"} class="text-amber-800 rounded-lg border-amber-700 p-2">
返回
</a>
<button class="text-amber-800 rounded-lg border-amber-700 px-2 py-1 bg-amber-300">送出</button>
</div>
</.form>
新增與修改的表格長的一模一樣,唯一的差別是 <.form>
的 action
屬性,我們可以將這個表格抽出來成為一個 component,並在新增與修改頁面中共用。
在 lib/gratitude_web/controllers/note_html.ex
裡新增一個接收 assigns 的 note_form
component 函式頭
defmodule GratitudeWeb.NoteHTML do
use GratitudeWeb, :html
embed_templates "note_html/*"
# 新增這一行
def note_form(assigns)
end
在 lib/gratitude_web/controllers/note_html/note_form.html.heex
中加上表格的內容,這邊使用 @action
替代掉兩個表格的唯一差異
<.form :let={form} for={@changeset} action={@action}>
<.input field={form[:content]} />
<div class="flex justify-between items-center mt-4">
<a href={~p"/notes"} class="text-amber-800 rounded-lg border-amber-700 p-2">
返回
</a>
<button class="text-amber-800 rounded-lg border-amber-700 px-2 py-1 bg-amber-300">送出</button>
</div>
</.form>
接著就可以在 new 與 edit 中使用 .note_form
component 取代掉原本的表格
New 頁面 (lib/gratitude_web/controllers/note_html/new.html.heex
)
<header class="my-4 border-b-4 border-amber-600">
<h1 class="text-xl font-bold text-amber-800">新增感激筆記</h1>
</header>
<.note_form changeset={@changeset} action={~p"/notes/"} />
Edit 頁面 (lib/gratitude_web/controllers/note_html/edit.html.heex
)
<header class="my-4 border-b-4 border-amber-600">
<h1 class="text-xl font-bold text-amber-800">編輯感激筆記</h1>
</header>
<.note_form changeset={@changeset} action={~p"/notes/#{@note}"} />
重構完成後可以執行測試,確認原本寫的新增有沒有壞掉。
參考新增流程的測試寫出修改流程的測試
參考解答
describe "edit note" do
test "顯示修改筆記表格", %{conn: conn} do
{:ok, note} = Notes.create_note(%{content: "修改前"})
conn = get(conn, ~p"/notes/#{note}/edit")
assert html_response(conn, 200) =~ "編輯感激筆記"
end
end
describe "update note" do
test "修改成功後轉址到列表", %{conn: conn} do
{:ok, note} = Notes.create_note(%{content: "修改前"})
conn = put(conn, ~p"/notes/#{note}", note: %{content: "修改後"})
assert redirected_to(conn) == ~p"/notes"
conn = get(conn, ~p"/notes")
html = html_response(conn, 200)
assert html =~ "感激筆記修改成功"
assert html =~ "修改後"
end
test "資料有誤時顯示錯誤", %{conn: conn} do
{:ok, note} = Notes.create_note(%{content: "修改前"})
conn = put(conn, ~p"/notes/#{note}", note: %{content: ""})
html = html_response(conn, 200)
assert html =~ "編輯感激筆記"
assert html =~ Plug.HTML.html_escape("can't be blank")
end
end